const contentPath = "/admin";

function ajax(url, opt, callback, fail) {
    let opts = {
        ...{
            method: "GET",
            headers: {
                'Content-Type': 'application/json'
            }
        }, ...opt
    };
    opts.headers['Authorization'] = 'Bearer ' + localStorage.getItem("Authorization");
    opts.headers["Content-Type"] === "application/json" && opts.body && (typeof opts.body !== "string") && (opts.body = JSON.stringify(opts.body));

    return fetch(contentPath + url, opts).then(function (response) {
        if (!response.ok) {
            if (response.status === 404)
                showToast("Can't connect to server.");
            else if (response.status === 500)
                showToast("Internal Server Error.");
            throw new Error(`HTTP error! Status: ${response.status}`);
        }
        return response.json();
    }).then(data => {
        if (data.code === 114) window.top.location = "/login.html"; else if (data.code === 115) showToast("Permission deny"); else if (data.code === 0) return data
        else
            throw new Error(`${JSON.stringify(data)}`);
    }).then(callback).catch(error => fail ? fail(error) : showToast(error));
}

async function ajaxAsync(url, opt) {
    let opts = {
        ...{
            method: "GET",
            headers: {
                'Authorization': 'Bearer ' + localStorage.getItem("Authorization"),
                'Content-Type': 'application/json'
            }
        }, ...opt
    };
    opts.body && (typeof opts.body !== "string") && (opts.body = JSON.stringify(opts.body));
    try {
        const response = await fetch(contentPath + url, opts);
        if (!response.ok) {
            throw new Error(`HTTP ${response.status} - ${response.statusText}`);
        }
        const contentType = response.headers.get("Content-Type");
        if (contentType && contentType.includes("application/json")) {
            return await response.json();
        } else {
            return await response.text();
        }
    } catch (err) {
        console.error("AJAX error:", err);
        throw err;
    }
}

/**
 * Convert a string to html Node.
 * When we create a <td> element, it can only in a element <tr>, tag should set to "tr"
 * @param str string
 * @param tag tag name use to wrapper the element.
 * @returns {ChildNode}
 * @private
 */
function _e(str, tag = "div") {
    let temp = document.createElement(tag || "div");
    temp.innerHTML = str;
    return temp.firstChild;
}

function loadCSS(href, id) {
    // Prevent loading the same CSS twice
    if (id && document.getElementById(id)) return;

    const link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = href;

    if (id) {
        link.id = id; // Optional ID to identify the stylesheet
    }
    document.head.appendChild(link);
}

function loadScript(src) {
    return new Promise((resolve, reject) => {
        if (document.querySelector(`script[src="${src}"]`)) {
            resolve(script);
        }
        const script = document.createElement('script');
        script.src = src;
        script.async = true;
        script.onload = () => resolve(script);
        script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
        document.head.appendChild(script);
    });
}

const delayLoad = (function () {
    const funcs = new Map();

    function run(func, delay, ...args) {
        if (funcs.has(func)) {
            clearTimeout(funcs.get(func));
            funcs.delete(func);
        }
        const timeoutId = setTimeout(() => {
            try {
                func(...args);
            } catch (e) {
                console.error("Error in delayed function:", e);
            }
            funcs.delete(func);
        }, delay);
        funcs.set(func, timeoutId);
    }

    return {run};
})();


/**
 * Append a node to a parentNode and at the first, if beforeNode is null, append to the last.
 * @param parentNode parentNode
 * @param node node
 * @private
 */
function _f(parentNode, node) {
    parentNode.firstChild ? parentNode.insertBefore(node, parentNode.firstChild) : parentNode.appendChild(node);
}

/**
 * Remove class name
 * @param className class name array
 * @returns {_rc}
 * @private
 */
function _rc(className) {
    if (this instanceof HTMLElement) {
        if (Array.isArray(className))
            this.classList.remove(...className);
        else if (typeof className === "string")
            this.classList.remove(className);
    }
    return this;
}

/**
 * Add class
 * @param className class name array
 * @returns {_ac}
 * @private
 */
function _ac(className) {
    if (this instanceof HTMLElement) {
        if (Array.isArray(className))
            this.classList.add(...className);
        else if (typeof className === "string")
            this.classList.add(className);
    }
    return this;
}

/**
 * Call a function with list
 * @param node NodeList or HttpElement or HttpCollection
 * @param callback function instance
 * @returns {*|*[]} array or element
 * @private
 */
function _a(node, callback) {
    let tmp = Array.from(arguments).slice(2);
    if (node instanceof NodeList) {
        return Array.from(node).map(node => {
            callback.apply(node, tmp);
        });
    } else if (node instanceof HTMLCollection) {
        return Array.from(node).map(node => callback.apply(node, tmp));
    } else if (node instanceof Array) {
        return node.map(node => node.apply(node, tmp));
    } else {
        return callback.apply(node, tmp);
    }
}

/**
 * Convert a string to html Node.
 * When we create a <td> element, it can only in an element <tr>, tag should set to "tr"
 * @param str string
 * @param tag tag name use to wrapper the element.
 * @returns {HTMLElement}
 * @private
 */
function _e(str, tag = "div") {
    let temp = document.createElement(tag || "div");
    temp.innerHTML = str;
    return temp.firstChild;
}

function showToast(message, status = "danger", delay = 3000) {
    let c = document.getElementById("_toastContainer");
    if (!c) {
        c = _e(`<div class="toast-container position-fixed bottom-0 end-0 p-3" id="_toastContainer"></div>`);
        document.body.appendChild(c);
    }
    if (status === "")
        status = "danger";
    const toastContainer = document.getElementById('_toastContainer');
    const toastEl = document.createElement('div');
    toastEl.className = `toast align-items-center text-bg-${status} breceive-0`;
    toastEl.setAttribute('role', 'alert');
    toastEl.setAttribute('aria-live', 'assertive');
    toastEl.setAttribute('aria-atomic', 'true');
    toastEl.innerHTML = `
    <div class="d-flex overflow-auto" style="max-height:20rem">
      <div class="toast-body">${message}</div>
      <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
    </div>
  `;
    toastContainer.appendChild(toastEl);
    if (delay < 0) {
        const toast = new bootstrap.Toast(toastEl, {autohide: false});
        toast.show();
    } else {
        const toast = new bootstrap.Toast(toastEl, {delay});
        toast.show();
    }
}

function overlay(show) {
    let mask = document.getElementById("overlay");
    if (!show) {
        mask && mask.remove();
        return;
    }
    if (!mask) {
        mask = _e(`<div id="overlay" class="d-flex justify-content-center align-items-center">
            <div class="text-center text-white">
              <div class="spinner-border mb-3" role="status"></div>
              <div>Processing...</div>
            </div>
        </div>`);
        document.body.appendChild(mask);
    }
    mask.style.display = "flex";
}


function subString(str, start, end) {
    return (str && str.length > end) ? str.substring(start, end) : (str || "")
}